﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using xEngine.DataTypes;

namespace xEngine.Input
{
    public class InputManager
    {
        #region Member Variables

        private KeyDefinitionCollection _definitions;
        private List<Keys> _definitionLocks;
        private List<Keys> _genericLocks;

        private MouseState _lastMouseState;
        private bool _calculateDeltaFromCenter;
        private bool _firstMouseCycle;

        #endregion

        #region Constructors

        public InputManager(Point windowCenter)
        {
            _definitions = new KeyDefinitionCollection();
            _definitionLocks = new List<Keys>();
            _genericLocks = new List<Keys>();

            WindowCenter = windowCenter;
            InitialMouseCentering = _firstMouseCycle = _calculateDeltaFromCenter = true;
        }

        #endregion

        #region Properties

        public bool InitialMouseCentering { get; set; }
        public Point WindowCenter { get; set; }

        #endregion

        #region Functions

        // .NET 4.0 introduced .HasFlag() method, but its significantly slower since it converts to UInt64
        public static bool IsMouseButton(MouseButton buttons, MouseButton button)
        {
            return (buttons & button) == button;
        }

        public void ProcessKeyboard()
        {
            KeyboardState state = Keyboard.GetState();

            // Process generic key events
            Keys[] keys = state.GetPressedKeys();
            foreach (Keys key in keys)
                if (!_genericLocks.Contains(key))
                {
                    KeyEventArgs e = new KeyEventArgs(key);
                    OnKeyDown(e);

                    if (e.ApplyLock && !_genericLocks.Contains(key))
                        _genericLocks.Add(key);
                }
            for (int i = _genericLocks.Count - 1; i >= 0; i--)
                if (state.IsKeyUp(_genericLocks[i]))
                {
                    OnKeyUp(new KeyEventArgs(_genericLocks[i]));
                    _genericLocks.RemoveAt(i);
                }
            // Processes each definition
            foreach (KeyDefinition definition in _definitions)     

                // Process only if lock is not set for this key
                if (state.IsKeyDown(definition.Key) && IsModifier(definition.Modifier) && !_definitionLocks.Contains(definition.Key))
                {
                    // Fire event for current key
                    KeyDefinitionEventArgs e = new KeyDefinitionEventArgs();
                    definition.EventHandler(this, e);

                    // Apply KeyLock if set so
                    if (e.ApplyLock && !_definitionLocks.Contains(definition.Key))
                        _definitionLocks.Add(definition.Key);
                }

            // Removes KeyLocks
            for (int i = _definitionLocks.Count - 1; i >= 0; i--)
                if (state.IsKeyUp(_definitionLocks[i]))
                    _definitionLocks.RemoveAt(i);
        }
        public void ProcessMouse()
        {
            // If initial mouse centering was set to true and it's the initial run, make sure mouse is in the middle
            if (InitialMouseCentering && _firstMouseCycle)
            {
                Mouse.SetPosition(WindowCenter.X, WindowCenter.Y);
                _firstMouseCycle = false;
            }

            // Get current mouse state
            MouseState currentState = Mouse.GetState();

            // No change? ignore
            if (currentState.Equals(_lastMouseState))
                return;

            // Current position
            Point position = new Point(currentState.X, currentState.Y);

            // Delta position
            PointF delta = new PointF(currentState.X - (_calculateDeltaFromCenter ? WindowCenter.X : _lastMouseState.X),
                                      currentState.Y - (_calculateDeltaFromCenter ? WindowCenter.Y : _lastMouseState.Y));
            
            // Mouse wheel delta
            int wheel = currentState.ScrollWheelValue - _lastMouseState.ScrollWheelValue;

            // Get buttons states
            MouseButtonState l = new MouseButtonState(currentState.LeftButton, _lastMouseState.LeftButton),
                             r = new MouseButtonState(currentState.RightButton, _lastMouseState.RightButton),
                             m = new MouseButtonState(currentState.MiddleButton, _lastMouseState.MiddleButton),
                             x1 = new MouseButtonState(currentState.XButton1, _lastMouseState.XButton1),
                             x2 = new MouseButtonState(currentState.XButton2, _lastMouseState.XButton2);

            // Mouse Wheel event ?
            if (wheel != 0)
                OnMouseWheel(new MouseWheelEventArgs(position, wheel));

            // Mouse Down event ?
            if (l.WasPressed() || r.WasPressed() || m.WasPressed() || x1.WasPressed() || x2.WasPressed())
                OnMouseDown(new MouseButtonEventArgs(position, l, r, m, x1, x2));

            // Mouse Up event ?
            if (l.WasReleased() || r.WasReleased() || m.WasReleased() || x1.WasReleased() || x2.WasReleased())
                OnMouseUp(new MouseButtonEventArgs(position, l, r, m, x1, x2));

            // Mouse Moved event ?
            if (delta.X != 0 || delta.Y != 0)
            {
                // Get mouse button states
                MouseButton mb = MouseButton.None;

                if (l.CurrentState == ButtonState.Pressed)
                    mb |= MouseButton.Left;
                if (r.CurrentState == ButtonState.Pressed)
                    mb |= MouseButton.Right;
                if (m.CurrentState == ButtonState.Pressed)
                    mb |= MouseButton.Middle;
                if (x1.CurrentState == ButtonState.Pressed)
                    mb |= MouseButton.X1;
                if (x2.CurrentState == ButtonState.Pressed)
                    mb |= MouseButton.X2;

                MouseMovedEventArgs e = new MouseMovedEventArgs(position, delta, mb);

                OnMouseMoved(e);

                if (_calculateDeltaFromCenter = e.ResetToCenter)
                    Mouse.SetPosition(WindowCenter.X, WindowCenter.Y);
            }

            // Save current mouse state
            _lastMouseState = currentState;
        }
        public void ResetMouse()
        {
            _firstMouseCycle = true;
        }

        public bool IsKeyDown(Keys key)
        {
            return Keyboard.GetState().IsKeyDown(key);
        }

        private bool IsModifier(KeyModifier mod)
        {
            KeyModifier m = KeyModifier.None;
            KeyboardState state = Keyboard.GetState();

            if (mod == KeyModifier.Any)
                return true;

            // Check Alt
            if (state.IsKeyDown(Keys.LeftAlt) || state.IsKeyDown(Keys.RightAlt))
                m |= KeyModifier.Alt;

            // Check Shift
            if (state.IsKeyDown(Keys.LeftShift) || state.IsKeyDown(Keys.RightShift))
                m |= KeyModifier.Shift;

            // Check Ctrl
            if (state.IsKeyDown(Keys.LeftControl) || state.IsKeyDown(Keys.RightControl))
                m |= KeyModifier.Ctrl;

            return m == mod;
        }

        #endregion

        #region Events

        public event EventHandler<MouseMovedEventArgs> MouseMoved;
        public event EventHandler<MouseButtonEventArgs> MouseDown;
        public event EventHandler<MouseButtonEventArgs> MouseUp;
        public event EventHandler<MouseWheelEventArgs> MouseWheel;
        public event EventHandler<KeyEventArgs> KeyDown;
        public event EventHandler<KeyEventArgs> KeyUp;

        public void AddKeyEventHandler(Keys key, EventHandler<KeyDefinitionEventArgs> keyEvent)
        {
            AddKeyEventHandler(key, KeyModifier.Any, keyEvent);
        }
        public void AddKeyEventHandler(Keys key, KeyModifier modifier, EventHandler<KeyDefinitionEventArgs> keyEvent)
        {
            _definitions.AddDefinition(key, keyEvent, modifier);
        }
        public void AddKeyEventHandler(string key, EventHandler<KeyDefinitionEventArgs> keyEvent)
        {
            AddKeyEventHandler(key, KeyModifier.Any, keyEvent);
        }
        public void AddKeyEventHandler(string key, KeyModifier modifier, EventHandler<KeyDefinitionEventArgs> keyEvent)
        {
            _definitions.AddDefinition(key, keyEvent, modifier);
        }
        public void RemoveKeyEventHandler(Keys key)
        {
            _definitions.RemoveDefinition(key);
        }
        public void RemoveKeyEventHandler(string key)
        {
            _definitions.RemoveDefinition(key);
        }

        private void OnMouseMoved(MouseMovedEventArgs e)
        {
            if (MouseMoved != null)
                MouseMoved(this, e);
        }
        private void OnMouseDown(MouseButtonEventArgs e)
        {
            if (MouseDown != null)
                MouseDown(this, e);
        }
        private void OnMouseUp(MouseButtonEventArgs e)
        {
            if (MouseUp != null)
                MouseUp(this, e);
        }
        private void OnMouseWheel(MouseWheelEventArgs e)
        {
            if (MouseWheel != null)
                MouseWheel(this, e);
        }
        private void OnKeyUp(KeyEventArgs e)
        {
            if (KeyUp != null)
                KeyUp(this, e);
        }
        private void OnKeyDown(KeyEventArgs e)
        {
            if (KeyDown != null)
                KeyDown(this, e);
        }

        #endregion
    }

    [Flags] public enum MouseButton
    {
        None = 0x0,
        Left = 0x1,
        Right = 0x2,
        Middle = 0x4,
        X1 = 0x8,
        X2 = 0x10
    }

    [Flags] public enum KeyModifier
    {
        Any = 0x00,
        None = 0x01,
        Alt = 0x02,
        Ctrl = 0x04,
        Shift = 0x08
    }

    public struct MouseButtonState
    {
        public ButtonState CurrentState;
        public ButtonState LastState;

        public MouseButtonState(ButtonState current, ButtonState last)
        {
            CurrentState = current;
            LastState = last;
        }

        public bool IsPressed()
        {
            return CurrentState == ButtonState.Pressed;
        }

        public bool HasChanged()
        {
            return !CurrentState.Equals(LastState);
        }
        public bool WasPressed()
        {
            return HasChanged() && CurrentState == ButtonState.Pressed && LastState == ButtonState.Released;
        }
        public bool WasReleased()
        {
            return HasChanged() && CurrentState == ButtonState.Released && LastState == ButtonState.Pressed;
        }

        public override string ToString()
        {
            return "CurrentState = " + CurrentState + ", LastState = " + LastState;
        }
    }
}
